package jef.database;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.ArrayList;
import java.util.List;

import jef.database.dialect.DatabaseDialect;
import jef.database.dialect.type.ColumnMapping;
import jef.database.meta.Feature;
import jef.database.meta.ITableMetadata;
import jef.database.meta.MetaHolder;
import jef.database.routing.PartitionResult;
import jef.database.support.SqlLog;
import jef.database.wrapper.clause.InsertSqlClause;
import jef.database.wrapper.processor.InsertStep.OracleRowidKeyCallback;
import jef.database.wrapper.variable.BindVariableContext;
import jef.tools.ArrayUtils;
import jef.tools.StringUtils;

abstract class InsertProcessor {
	protected DbClient db;

	static InsertProcessor get(DatabaseDialect profile, DbClient db) {
		return new PreparedImpl(db);
	}

	/**
	 * generate a insert SQL.
	 * 
	 * @param obj
	 * @param tableName
	 * @param dynamic
	 * @param extreme
	 * @return
	 * @throws SQLException
	 */
	abstract InsertSqlClause toInsertSql(IQueryableEntity obj, String tableName, boolean dynamic, PartitionResult pr) throws SQLException;

	abstract InsertSqlClause toInsertSqlBatch(IQueryableEntity obj, String tableName, boolean dynamic, boolean extreme, PartitionResult pr) throws SQLException;

	/**
	 * process insert operate
	 * 
	 * @param db
	 * @param obj
	 * @param sqls
	 * @param start
	 * @param parse
	 * @throws SQLException
	 */
	abstract void processInsert(OperateTarget db, IQueryableEntity obj, InsertSqlClause sqls, long start, long parse) throws SQLException;

	/**
	 * 构造
	 * 
	 * @param parentDbClient
	 * @param rProcessor
	 */
	InsertProcessor(DbClient parentDbClient) {
		this.db = parentDbClient;
	}

	static final class PreparedImpl extends InsertProcessor {
		PreparedImpl(DbClient parentDbClient) {
			super(parentDbClient);
		}

		@Override
		InsertSqlClause toInsertSql(IQueryableEntity obj, String tableName, boolean dynamic, PartitionResult pr) throws SQLException {
			return toInsertSqlBatch(obj, tableName, dynamic, false, pr);
		}

		@Override
		InsertSqlClause toInsertSqlBatch(IQueryableEntity obj, String tableName, boolean dynamic, boolean extreme, PartitionResult pr) throws SQLException {
			DatabaseDialect profile = pr == null ? db.getProfile(null) : db.getProfile(pr.getDatabase());
			List<String> cStr = new ArrayList<String>();// 字段列表
			List<String> vStr = new ArrayList<String>();// 值列表
			ITableMetadata meta = MetaHolder.getMeta(obj);
			InsertSqlClause result = new InsertSqlClause(extreme);
			result.parent = db;
			result.profile = profile;
			result.setTableNames(pr);
			for (ColumnMapping entry : meta.getColumns()) {
				if(entry.isNotInsert())
					continue;
				entry.processPreparedInsert(obj, cStr, vStr, result, dynamic);
			}
			if (profile.has(Feature.SELECT_ROW_NUM) && !extreme) {
				result.getCallback().addProcessor(new OracleRowidKeyCallback());
			}
			result.setColumnsPart(StringUtils.join(cStr, ','));
			result.setValuesPart(StringUtils.join(vStr, ','));
			return result;
		}

		@Override
		void processInsert(OperateTarget db, IQueryableEntity obj, InsertSqlClause sqls, long start, long parse) throws SQLException {
			SqlLog sb = ORMConfig.getInstance().newLogger();
			String sql = sqls.getSql();
			sb.ensureCapacity(sql.length() + 128);
			sb.append(sql).append(db);
			PreparedStatement psmt = null;
			DatabaseDialect profile = db.getProfile();
			try {
				psmt = sqls.getCallback().doPrepareStatement(db, sql);
				BindVariableContext context = new BindVariableContext(psmt, profile, sb);
				context.setInsertVariables(obj, sqls.getFields());
				psmt.execute();
				sb.append("\nInsert:1\tTime cost([ParseSQL]:", parse - start).append("ms, [DbAccess]:", System.currentTimeMillis() - parse).append("ms)").append(db);
				sqls.getCallback().callAfter(obj);
			} catch (SQLIntegrityConstraintViolationException e) {
				throw e;
			} catch (SQLException e) {
				String s = profile.getViolatedConstraintNameExtracter().extractConstraintName(e);
				if (s != null) {
					throw new SQLIntegrityConstraintViolationException(s);
				} else {
					DbUtils.processError(e, ArrayUtils.toString(sqls.getTable(), true), db);
					throw e;
				}
			} finally {
				sb.output();
				if (psmt != null)
					psmt.close();
				db.releaseConnection();
			}
		}
	}
}
